﻿#define LP_CORE

#include "lp_timer.h"
#include "lp_service.h"
#include "lp_thread.h"

#ifdef LP_WINDOWS
#include <Windows.h>
#else
#include <sys/time.h>
#endif

#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>


#define TIME_NEAR_SHIFT     8
#define TIME_NEAR           (1 << TIME_NEAR_SHIFT)
#define TIME_LEVEL_SHIFT    8
#define TIME_LEVEL          (1 << TIME_LEVEL_SHIFT)
#define TIME_NEAR_MASK      (TIME_NEAR - 1)
#define TIME_LEVEL_MASK     (TIME_LEVEL - 1)

typedef struct Node Node;
struct Node{
    Node* next;
    uint64_t expire;
    LPService h;
    LPSession session;
};

typedef struct{
    Node head;
    Node * tail;
}List;

static struct{
    LPSpinLock lock;
    List n[TIME_NEAR];
    List t[7][TIME_LEVEL];
    uint64_t time;
    uint64_t stime;
    uint64_t current;
    uint64_t currentPoint;

#ifdef LP_WINDOWS
    LARGE_INTEGER counter;
    LARGE_INTEGER frequency;
    uint64_t startup;
#endif
}gCtx = { 0 };


static inline Node *
clearList (List * l){
    Node * r = l->head.next;
    l->head.next = NULL;
    l->tail = &(l->head);
    return r;
}

static inline void
linkList (List * l, Node * n){
    l->tail->next = n;
    l->tail = n;
    n->next = NULL;
}

static void
addNode (Node * n){
    uint64_t time = n->expire;
    if((time | TIME_NEAR_MASK) == (gCtx.time | TIME_NEAR_MASK)){
        linkList(&(gCtx.n[time & TIME_NEAR_MASK]), n);
    }else{
        int i = 0;
        uint64_t mask = TIME_NEAR << TIME_LEVEL_SHIFT;
        for(i = 0; i < 13; i ++){
            if((time | (mask - 1)) == (gCtx.time | (mask - 1))){
                break;
            }
            mask <<= TIME_LEVEL_SHIFT;
        }
        int shift = TIME_NEAR_SHIFT + i * TIME_LEVEL_SHIFT;
        linkList(&(gCtx.t[i][(time >> shift) & TIME_LEVEL_MASK]), n);
    }
}

static void
moveList (int level, int idx){
    Node * n = clearList(&(gCtx.t[level][idx]));
    while(n){
        Node * t = n->next;
        addNode(n);
        n = t;
    }
}

static void
shiftTimer (){
    int mask = TIME_NEAR;
    uint64_t ct = ++ gCtx.time;
    if(ct == 0){
        moveList(3, 0);
    }else{
        uint64_t t = ct >> TIME_NEAR_SHIFT;
        int i = 0;
        while((ct & (mask - 1)) == 0){
            int idx = t & TIME_LEVEL_MASK;
            if(idx != 0){
                moveList(i, idx);
                break;
            }
            mask <<= TIME_LEVEL_SHIFT;
            t >>= TIME_LEVEL_SHIFT;
            i ++;
        }
    }
}

static void
dispatch (Node * n){
    do{
        LPMsgSend send = { 0 };
        send.ptype = LP_PTYPE_TIMEOUT;
        send.cmd = 0;
        send.session = n->session;
        lpSend(n->h, &send);
        // ddcl_send(n->e.source, 0, DDCL_PTYPE_RESP,
                // DDCL_CMD_TIMEOUT, n->e.session, NULL, 0);
        Node * t = n;
        n = n->next;
        free(t);
    } while (n);
}

static void
executeTimer (){
    int idx = gCtx.time & TIME_NEAR_MASK;
    while (gCtx.n[idx].head.next) {
        Node * n = clearList(&(gCtx.n[idx]));
        lpUnlockSpin(&(gCtx.lock));
        // dispatch timeout msg
        dispatch(n);
        lpLockSpin(&(gCtx.lock));
    }
}

static void
updateTimer (){
    lpLockSpin(&gCtx.lock);
    executeTimer();
    shiftTimer();
    executeTimer();
    lpUnlockSpin(&gCtx.lock);
}


LP_API void
lpInitTimerContext (){
#ifdef LP_WINDOWS
    QueryPerformanceCounter(&gCtx.counter);
    QueryPerformanceFrequency(&gCtx.frequency);
    gCtx.startup = lpTime();
#endif
    lpInitSpin(&gCtx.lock);
    gCtx.currentPoint = lpTime();
    for(int i = 0; i < TIME_NEAR; i ++){
        clearList(&(gCtx.n[i]));
    }
    for(int i = 0; i < 7; i ++){
        for(int j = 0; j < TIME_LEVEL; j ++){
            clearList(&(gCtx.t[i][j]));
        }
    }
}


LP_API void
lpExitTimeContext (){
    Node * node;
    Node * tmp;
    for(int i = 0; i < TIME_NEAR; i ++){
        node = gCtx.n[i].head.next;
        while (node) {
            tmp = node;
            node = node->next;
            free(tmp);
        }
    }
    for(int i = 0; i < 7; i ++){
        for(int j = 0; j < TIME_LEVEL; j ++){
            node = gCtx.t[i][j].head.next;
            while (node) {
                tmp = node;
                node = node->next;
                free(tmp);
            }
        }
    }
}

LP_API int
lpDispatchTimer (){
    uint64_t ct = lpTime();
    if (ct <= gCtx.currentPoint) {
        //printf("time diff error: change from %lld to %lld \n", ct, gCtx.currentPoint);
        gCtx.currentPoint = ct;
        return 0;
    }
    uint32_t diff = (uint32_t)(ct - gCtx.currentPoint);
    gCtx.currentPoint = ct;
    gCtx.current += diff;
    for (uint32_t i = 0; i < diff; i++) {
        updateTimer();
    }
    return 1;
}

// LP_API uint64_t
// lpNow (LPContext * context){
    // LPTimerContext * timer = context->timer;
// #ifdef LP_WINDOWS
    // // lpLockSpin(&gCtx.lock);
    // LARGE_INTEGER li;
    // QueryPerformanceCounter(&li);
    // double s = ((double)(li.QuadPart - gCtx.counter.QuadPart) / gCtx.frequency.QuadPart) * 1000;
    // // lpUnlockSpin(&gCtx.lock);
    // return gCtx.startup + (uint64_t)s;
// #else
    // struct timeval tv;
    // gettimeofday(&tv, NULL);
    // return tv.tv_sec * 1000 + tv.tv_usec / 1000;
// #endif
// }

LP_API uint64_t
lpTime(){
#ifdef LP_WINDOWS
    time_t t = time(NULL);
    SYSTEMTIME st;
    GetSystemTime(&st);
    return t * 1000 + st.wMilliseconds;
#else
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec * 1000 + tv.tv_usec / 1000;
#endif
}

LP_API void
lpTimeout(LPService h, LPSession session, uint32_t ms){
    if (ms > 0) {
        Node * n = malloc(sizeof(Node));
        n->session = session;
        n->h = h;
        lpLockSpin(&gCtx.lock);
        n->expire = gCtx.time + ms - 1;
        addNode(n);
        lpUnlockSpin(&gCtx.lock);
    }else {
        LPMsgSend send = { 0 };
        send.ptype = LP_PTYPE_TIMEOUT;
        send.cmd = 0;
        send.session = session;
        lpSend(h, &send);
    }
}
